using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Web;
using NWrapper.Attributes;

namespace NWrapper
{
    /// <summary>
    /// Helper methods for reflection.
    /// </summary>
	public static class ReflectionHelper
	{
        /// <summary>
        /// Gets the assembly path to a given named assembly based on the runtime.
        /// </summary>
        /// <param name="assemblyName">Name of the assembly.</param>
        /// <returns>The full path to the assembly.</returns>
        /// <remarks>
        /// This method will add ".dll" to the assembly name if needed, and will search in the 
        /// <see cref="Environment.CurrentDirectory"/> as well as the <see cref="HttpRuntime.BinDirectory"/>.
        /// </remarks>
		public static string GetAssemblyPath(string assemblyName)
		{
            if (!assemblyName.EndsWith(".dll"))
            {
                assemblyName += ".dll";
            }

            if (HttpContext.Current != null)
            {
                assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName);
            }
            else
            {
                assemblyName = Path.Combine(Environment.CurrentDirectory, assemblyName);
            }

			return assemblyName;
		}

        /// <summary>
        /// Creates an instance of a class as defined in a given assembly.
        /// </summary>
        /// <typeparam name="T">The type of class to return. <paramref name="classname"/> must be assignable to this.</typeparam>
        /// <param name="assemblyName">Name of the assembly which contains the class.</param>
        /// <param name="className">Name of the class to create.</param>
        /// <returns>The new instance.</returns>
		public static T GetObject<T>(string assemblyName, string className)
		{
			assemblyName = GetAssemblyPath(assemblyName);

			Assembly assembly = Assembly.LoadFrom(assemblyName);
			if (assembly == null)
				throw new NullReferenceException("Unable to load assembly " + assemblyName);

			Type type = assembly.GetType(className);
			if (type == null)
				throw new NullReferenceException("Unable to load type " + className);

			if (type.GetCustomAttributes(typeof(SingletonAttribute), false).Length != 0)
			{
				PropertyInfo prop = type.GetProperty("Instance");
				if (prop == null)
                    throw new NullReferenceException("Static property 'Instance' does not exist on type " + className);

				object obj = prop.GetValue(null, null);
                if (obj is T)
                    return (T)obj;
                else
                    throw new InvalidCastException(
                        String.Format("Unable to get value for static property 'Instance' on type {0}. Class was expected to be of type {1}.",
                        className, typeof(T).ToString()));
			}
			else
			{
				object instance = assembly.CreateInstance(className);
                if (instance is T)
                    return (T)instance;
                else
                    throw new InvalidCastException(
                        String.Format("Unable to create an instance of class {0}. Class was expected to be of type {1}.",
                        className, typeof(T).ToString()));
			}
		}
	}
}
